//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using OpenLS.Spreadsheet.UI.Internals;
using OpenLS.Spreadsheet.UI.Internals.Resources;

namespace OpenLS.Spreadsheet.UI
{
    internal static class DragDropManager
    {
        public static readonly DependencyProperty IsDownProperty =
            DependencyProperty.RegisterAttached("IsDown", typeof (bool), typeof (DragDropManager),
                                                new UIPropertyMetadata(false));

        public static readonly DependencyProperty DragOffsetProperty =
            DependencyProperty.RegisterAttached("DragOffset", typeof (object), typeof (WorksheetGrid),
                                                new UIPropertyMetadata(null));

        public static readonly RoutedEvent PreviewDragOverEvent = EventManager.RegisterRoutedEvent("PreviewDragOver",
                                                                                                   RoutingStrategy.
                                                                                                       Tunnel,
                                                                                                   typeof (
                                                                                                       InternalDragEventHandler
                                                                                                       ),
                                                                                                   typeof (
                                                                                                       DragDropManager));

        public static readonly RoutedEvent PreviewDropEvent = EventManager.RegisterRoutedEvent("PreviewDrop",
                                                                                               RoutingStrategy.Tunnel,
                                                                                               typeof (
                                                                                                   InternalDragEventHandler
                                                                                                   ),
                                                                                               typeof (DragDropManager));

        public static readonly DependencyProperty StartPointProperty =
            DependencyProperty.RegisterAttached("StartPoint", typeof (Point), typeof (DragDropManager),
                                                new UIPropertyMetadata(new Point()));

        private static FrameworkElement _dragDropAdvisor;
        private static InternalDataObject _draggedObject;
       /* private static bool _isDragging;

        public static bool IsDragging
        {
            get { return _isDragging; }
            set { _isDragging = value; }
        }*/
        internal static UIElement DragSourceElement;

        public static bool IsDragging
        {
            get { return DragSourceElement != null; }
        }

        public static Point GetStartPoint(DependencyObject obj)
        {
            return (Point) obj.GetValue(StartPointProperty);
        }

        public static void SetStartPoint(DependencyObject obj, Point value)
        {
            obj.SetValue(StartPointProperty, value);
        }

        // Using a DependencyProperty as the backing store for StartPoint.  This enables animation, styling, binding, etc...


        public static bool GetIsDown(DependencyObject obj)
        {
            return (bool) obj.GetValue(IsDownProperty);
        }

        public static void SetIsDown(DependencyObject obj, bool value)
        {
            obj.SetValue(IsDownProperty, value);
        }


        public static void EnableEvents(UIElement g)
        {
            g.PreviewDragEnter += (gridPreviewDragEnter);
            g.PreviewDragOver += (gridPreviewDragOver);
            g.PreviewDrop += (gridPreviewDrop);
            g.PreviewDragLeave += (gridPreviewDragLeave);
            g.AddHandler(PreviewDropEvent, new InternalDragEventHandler(onCommonDrop));
            g.AddHandler(PreviewDragOverEvent, new InternalDragEventHandler(onPreviewDragOver));
            g.AllowDrop = true;
        }

        private static void enableAdviser(UIElement e)
        {
            Mouse.Capture(e);
            e.PreviewMouseUp += advisorPreviewMouseUp;
            e.PreviewMouseMove += advisorPreviewMouseMove;
        }

        private static void advisorPreviewMouseMove(object sender, MouseEventArgs e)
        {
            var element = (FrameworkElement) sender;
            element.RaiseEvent(new InternalDragEventArgs(
                                   PreviewDragOverEvent, _draggedObject, DragDropKeyStates.None, DragDropEffects.Move,
                                   element, e.GetPosition(element)));
        }

        private static void disableAdviser(UIElement e)
        {
            Mouse.Capture(null);
            e.PreviewMouseUp -= advisorPreviewMouseUp;
            e.PreviewMouseMove -= advisorPreviewMouseMove;
        }

        private static void advisorPreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            var element = (FrameworkElement) sender;
            element.RaiseEvent(new InternalDragEventArgs(
                                   PreviewDropEvent, _draggedObject, DragDropKeyStates.None, DragDropEffects.Move,
                                   element, e.GetPosition(element)));
            disableAdviser(element);
        }

        private static void gridPreviewDragLeave(object sender, DragEventArgs e)
        {
            if (updateEffects(new ExternalDragEventArgs(e)))
            {
                SpreadsheetElement.SetDropTargetFor(sender, null);
                e.Handled = true;
            }
        }


        private static Worksheet getWorksheet(object sender)
        {
            return WorksheetHelper.GetWorksheet(sender);
        }

        private static void gridPreviewDrop(object sender, DragEventArgs e)
        {
            var ee = new ExternalDragEventArgs(e);
            if (updateEffects(ee))
            {
                onCommonDrop(sender, ee);
            }
        }

        private static void onCommonDrop(object sender, BaseDragEventArgs e)
        {
            var addressLabel = e.GetData(DragDropFormats.SpreadsheetSelection) as string;
            var grid = (WorksheetGrid) sender;
            WorksheetGrid sourceGrid = DragDropManager.DragSourceElement as WorksheetGrid;
            if (sourceGrid == null)
                sourceGrid = grid;
            Worksheet worksheet = getWorksheet(sender);
            Range obj = worksheet.GetRange(addressLabel);
            Point pt = e.GetPosition(grid);
            CellAddress target = grid.GetCellAddressFrom(e.GetPosition);
            var offset = (CellRelativeAddress) getDragOffset(sourceGrid);
            target = target - offset;
            switch (e.Effects)
            {
                case DragDropEffects.Copy:
                    using (worksheet.Workbook.CreateUndoScope(UIStringTable.Move))
                    {
                        worksheet[target].ToRange().CopyFrom(obj, new PasteInfo(RangeFunctions.ApplyAll, false, false));
                    }
                    break;
                case DragDropEffects.Move:
                    {
                        Range rTarget = obj.Offseted(target - obj.TopLeftCell.Address);
                        using (obj.Worksheet.CreateUndoScope(UIStringTable.DragDrop))
                        {
                            if (rTarget.Address.TopLeft == obj.TopLeftCell.Address)
                            {
                                DragSourceElement = null;
                                SpreadsheetElement.SetDropTargetFor(sender, null); 
                                return;
                            }
                            var targetRange = obj.Offseted(rTarget.Address.TopLeft - obj.TopLeftCell.Address);
                            if (targetRange.IsProtected || obj.IsProtected)
                            {
                                SpreadsheetElement.OnProtectionViolation(sender as UIElement);
                                DragSourceElement = null;
                                SpreadsheetElement.SetDropTargetFor(sender, null);
                                return;
                            }
                            worksheet.MoveRange(obj, rTarget.TopLeftCell.Address);
                            
                        }
                    }
                    break;
            }
            worksheet.Selection = obj.Offseted(target - obj.TopLeftCell.Address);
            DragSourceElement = null;
            SpreadsheetElement.SetDropTargetFor(sender, null);
        }


        private static void gridPreviewDragOver(object sender, DragEventArgs e)
        {
            onPreviewDragOver(sender, new ExternalDragEventArgs(e));
        }

        private static void onPreviewDragOver(object sender, BaseDragEventArgs e)
        {
            updateTarget(sender, e);
        }
        private static void updateTarget(object sender, BaseDragEventArgs e)
        {
            if (updateEffects(e))
            {
                var grid = (WorksheetGrid) sender;
                WorksheetGrid sourceGrid = DragSourceElement as WorksheetGrid;
                if (sourceGrid == null)
                    sourceGrid = grid;
                CellAddress targetAddress = grid.GetCellAddressAt(e.GetPosition(grid));
                Range source = getWorksheet(grid).GetRange(e.GetData(DragDropFormats.SpreadsheetSelection).ToString());
                CellRelativeAddress move = targetAddress - source.TopLeftCell.Address;
                var offset = (CellRelativeAddress)getDragOffset(sourceGrid);
                CellRelativeAddress m = move - offset;
                m = new CellRelativeAddress(Math.Max(m.Row, -source.TopLeftCell.Row.Index),
                                            Math.Max(m.Column, -source.TopLeftCell.Column.Index));
                Range targetRange = source.Offseted(m);
                SpreadsheetElement.SetDropTargetFor(sender, targetRange);
                e.Handled = true;
            }
        }


        private static void gridPreviewDragEnter(object sender, DragEventArgs e)
        {
            updateTarget(sender, new ExternalDragEventArgs(e));
        }


        private static object getDragOffset(DependencyObject obj)
        {
            return obj.GetValue(DragOffsetProperty);
        }

        public static void SetDragOffset(DependencyObject obj, object value)
        {
            obj.SetValue(DragOffsetProperty, value);
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...


        private static bool updateEffects(BaseDragEventArgs e)
        {
            if (!e.GetDataPresent(DragDropFormats.SpreadsheetSelection))
            {
                e.Effects = DragDropEffects.None;
                return false;
            }
            if (((e.AllowedEffects & DragDropEffects.Copy) == DragDropEffects.Copy) ||
                ((e.AllowedEffects & DragDropEffects.Move) == DragDropEffects.Move))
            {
                e.Effects = (e.KeyStates & DragDropKeyStates.ControlKey) == DragDropKeyStates.ControlKey
                                ? DragDropEffects.Copy
                                : DragDropEffects.Move;
            }
            else
            {
                e.Effects = e.AllowedEffects & ((DragDropEffects.Copy | DragDropEffects.Move) ^ (DragDropEffects.All));
            }
            return true;
        }


        internal static void StartDrag(UIElement grid)
        {
            DragSourceElement = grid;
        }

        internal static void DoDragDrop(WorksheetGrid grid, object data, DragDropEffects p)
        {
            if (data is IDataObject)
                DragDrop.DoDragDrop(grid, data, p);
            else
            {
                _draggedObject = (InternalDataObject) data;
                _dragDropAdvisor = grid;
                enableAdviser(_dragDropAdvisor);
            }
        }
    }
}